#include <dirent.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>

#include "soh.h"

/*****************************************************************
This file contains the functions responsible for indexing shares. 
The main idea is to save all the necessary information about all 
the folders and files in the share into RAM and be able to quickly 
give it to the client without having to go to the disk.
******************************************************************/

//this global vars is used to avoid 
//passing arguments in recursive calls
static struct stat g_tmpStat;

/*******************************
*            UTILS             *
*******************************/
static int dirSkipDots( DIR* dir ){
	
	int i;
	struct dirent* dirent;
	const char* dots[2] = { ".", ".." };
	
	rewinddir( dir );
	
	for( i = 0; i < 2; i++ ){
		dirent = readdir( dir );
		if( !dirent ){
			perror( __FUNCTION__ );
			return -1;
		}
		
		if( strcmp(dirent->d_name, dots[i]) ){
			printf( "%s: Missing dots in %i\n", __FUNCTION__, i );
			return -1;
		}
	}
	
	return 0;
}

static int qsort_compare_cb( const void* p1, const void* p2 ){
	
	char* desc1 = ((struct file_db*)p1)->desc;
	char* desc2 = ((struct file_db*)p2)->desc;
	
	//directories must be first in list
	if( (desc1[0] & FILEDESC_TYPE_MASK) == FILEDESC_TYPE_DIR && (desc2[0] & FILEDESC_TYPE_MASK) != FILEDESC_TYPE_DIR ) return -1;
	if( (desc1[0] & FILEDESC_TYPE_MASK) != FILEDESC_TYPE_DIR && (desc2[0] & FILEDESC_TYPE_MASK) == FILEDESC_TYPE_DIR ) return 1;
	
	//sorting
	return strcmp( desc1 + 1, desc2 + 1 );
}



/*******************************
*        OPEN DIR TREE         *
*******************************/

static int openTreeDirNode( struct file_db* node );
//0 ok
//-1 not fatal ( e.g. EACCES )
//-2 fatal     ( e.g. ENOMEM )
static inline int openTreeNode( char* nodeName, struct file_db* node ){
	
	int res;
	char type;
	
	//get info node
	if( lstat(nodeName, &g_tmpStat) ){
		perror( "stat" );
		return -2;
	}
	
	//convert stat type format to our type format
	res = convertStatType( &g_tmpStat, &type );
	if( res ) return -2;
	
	//save length
	node->size = g_tmpStat.st_size;
	
	//alloc memory for node description
	node->desc = customMalloc( strlen(nodeName) + 2 );
	if( !node->desc ){
		printf( "ERROR: cannot add node description.\n" );
		return -2;
	}
	
	//add node description name and type
	node->desc[0] = type;
	sprintf( &node->desc[1], "%s", nodeName );
	
	if( type == FILEDESC_TYPE_DIR  ) return openTreeDirNode(  node );
	if( type == FILEDESC_TYPE_FILE ){
		res = open( node->desc + 1, O_RDONLY );
		if( res < 0 ) return -1;
		close( res );
	}
	
	return 0;
}

//0 ok
//-1 not fatal
//-2 fatal
static int openTreeDirNode( struct file_db* node ){
	
	int res;
	DIR* dir;
	long long int counter;
	long long int direntCount;
	struct dirent* dirent;
	
	direntCount = 0;
	
	//open node as dir
	dir = opendir( &node->desc[1] );
	if( !dir ){
		if( errno != EACCES ) perror( "tree open dir" );
		return -1;
	}
	
	//cd into dir
	res = chdir( &node->desc[1] );
	if( res ){
		if( errno != EACCES ) perror( "tree cd dir" );
		goto l_closeDir;
	}
	
	//skip ./ and ../
	if( dirSkipDots(dir) ) goto l_exitNode;
	
l_calculateDirents:
	//сalculate the number of dirents
	dirent = readdir( dir );
	if( dirent ){
		direntCount++;
		goto l_calculateDirents;
	}
	
	//if dir is empty
	if( !direntCount ){
		node->entries = NULL;
		goto l_exitNode;
	}
	
	//memory alloc
	node->entries = malloc( sizeof(*node->entries) * direntCount );
	if( !node->entries ){
		printf( "tree: Cannot allocate memory.\n" );
		return -2;
	}
	
	//skip ./ and ../
	res = dirSkipDots( dir );
	if( res ){
		printf( "tree: skip dots failed\n" );
		res = -2;
		goto l_exitNode;
	}
	
	//read dir entries
	for( counter = 0; counter < direntCount; counter++ ){
		
		//read dir
		dirent = readdir( dir );
		if( !dirent ) break;
		
		//open node and handle result
		res = openTreeNode( dirent->d_name, &node->entries[counter] );
		if(      !res      ) node->size += node->entries[counter].size;
		else if( res == -1 ) counter--;
		else                 goto l_exitNode;
	}
	
	//TODO: multithreading sort(?)
	//TODO: free unneded memory
	if( counter > 0 ){
		
		//sort entries
		qsort( node->entries, counter, sizeof(*node->entries), qsort_compare_cb );
		
		//set last flag
		counter--;
		node->entries[counter].desc[0] |= FILEDESC_LAST_FLAG;
	}else{//if there are no read rights to any of the entries
		free( node->entries );
		node->entries = NULL;
	}
	
	res = 0;
	
l_exitNode:
	//exit from dir
	if( chdir("../") ){
		perror( "FATAL: tree back cd dir" );
		res = -2;
	}
	
l_closeDir:
	//close dir
	closedir( dir );
	
	return res;
}

int indexShare( struct instance* this ){
	
	int  res;
	char tmpbuf[32];
	char realSharePath[PATH_MAX];
	char partOfPath[PATH_MAX];
	size_t pathOffset;
	char* tmp;
	
	//init some vars
	pathOffset = 1;
	
	//just in case
	if( !this->sharePath ){
		printf( "Error: no input file or dir.\n" );
		return -2;
	}
	
	//get real path to share
	tmp = realpath( this->sharePath, realSharePath );
	if( !tmp ){
		perror( "get real share path error" );
		return -2;
	}
	
	//go to the root
	res = chdir( "/" );
	if( res < 0 ){
		perror( "cannot cd to share path" );
		return -2;
	}
	
	//if share is root, nothing else needs to be done
	if( !strcmp(realSharePath, "/") ){
		partOfPath[0] = '/';
		partOfPath[1] = 0x00;
		goto l_startShareIndex;
	}
	
l_parseSharePath:
	//get part of share path
	res = parserCore( realSharePath, '/', partOfPath, PATH_MAX, &pathOffset );
	if( res < 0 ){
		printf( "Failed to parse real share path.\n" );
		return -2;
	}
	
	//if it not last part of path
	if( res ){
		
		//cd to next dir
		res = chdir( partOfPath );
		if( res < 0 ){
			perror( "cannot cd to share path" );
			return -2;
		}
		
		//get next part of path
		goto l_parseSharePath;
	}
	
l_startShareIndex:
	
	//share file indexing
	printf( "Indexing %s... ", realSharePath );
	fflush( stdout );
	res = openTreeNode( partOfPath, &this->root );
	if( res < 0 ){
		printf( "Failed to index share.\n" );
		return -2;
	}
	
	printf( "done.\n" );
	
	//set last flag
	this->root.desc[0] |= FILEDESC_LAST_FLAG;
	
	//TODO: more info about share
	readableSize( tmpbuf, this->root.size );
	printf( "Total size %s\n", tmpbuf );
	
	return 0;
}
